package com.apes.framework.plugin.esb.processor.connector.http.httpclient;

import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.*;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

/**
 * 功能：HttpClient配置
 *
 * @author xul
 * @create 2020-04-22 10:04
 */
@Configuration
public class HttpClientConfig {

    /**
     * 连接池最大连接数
     */
    @Value("${http.client.maxTotal:200}")
    private Integer maxTotal = 200;

    /**
     * 并发数
     */
    @Value("${http.client.defaultMaxPerRoute:100}")
    private Integer defaultMaxPerRoute = 100;

    /**
     * 连接存活时间，单位：s
     */
    @Value("${http.client.timeToLive:60}")
    private Integer timeToLive = 60;

    /**
     * 可用空闲连接过期时间，重用空闲连接时会先检查是否空闲时间超过这个时间（单位：ms）
     */
    @Value("${http.client.validateAfterInactivity:30000}")
    private Integer validateAfterInactivity = 30000;


    /**
     * 实例化一个连接池管理器，设置最大连接数、并发连接数
     *
     * @return
     */
    @Bean(name = "httpClientConnectionManager")
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager(@Qualifier("socketFactoryRegistry") Registry<ConnectionSocketFactory> socketFactoryRegistry) {
        PoolingHttpClientConnectionManager httpClientConnectionManager =
                new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, timeToLive, TimeUnit.SECONDS);
        /**最大连接数 **/
        httpClientConnectionManager.setMaxTotal(maxTotal);
        /**并发数：设置过小无法支持大并发(ConnectionPoolTimeoutException: Timeout waiting for connection from pool) **/
        httpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
        /**定义不活动的时间段（单位：ms），之后必须在租用给使用者之前重新验证持久连接 **/
        /**传递给此方法的非正值会禁用连接验证 **/
        /**此检查有助于检测已经变为陈旧（半关闭）的连接，同时在池中保持不活动状态 **/
        httpClientConnectionManager.setValidateAfterInactivity(validateAfterInactivity);
        return httpClientConnectionManager;
    }

    @Bean("socketFactoryRegistry")
    public Registry<ConnectionSocketFactory> socketFactoryRegistry(@Qualifier("sslConnectionSocketFactory") SSLConnectionSocketFactory sslConnectionSocketFactory) {
        return RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", sslConnectionSocketFactory)
                .build();
    }

    @Bean("sslConnectionSocketFactory")
    public SSLConnectionSocketFactory sslConnectionSocketFactory(@Qualifier("sslContext") SSLContext sslContext) {
        return new SSLConnectionSocketFactory(
                sslContext,
                new String[]{"TLSv1", "TLSv1.2", "SSLv3"},
                null,
                //客户端验证服务器身份的策略
                NoopHostnameVerifier.INSTANCE
        );
    }

    @Bean("sslContext")
    public SSLContext sslContext(@Qualifier("customTrustManager") TrustManager customTrustManager) throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{customTrustManager}, null);
        return sslContext;
    }

    /**
     * 自定义信任证书管理器
     */
    @Bean("customTrustManager")
    public TrustManager customTrustManager() {
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
    }


    /**
     * 会话保持时间（单位：秒）
     */
    @Value("${http.client.keepAliveTime:20}")
    private int keepAliveTime = 20;

    @Bean("connectionKeepAliveStrategy")
    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (httpResponse, httpContext) -> {
            // Honor 'keep-alive' header
            HeaderElementIterator iterator = new BasicHeaderElementIterator(
                    httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (iterator.hasNext()) {
                HeaderElement headerElement = iterator.nextElement();
                String param = headerElement.getName();
                String value = headerElement.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    try {
                        return Long.parseLong(value) * 1000;
                    } catch (NumberFormatException ignore) {
                        throw new RuntimeException(ignore);
                    }
                }
            }
            return keepAliveTime * 1000;
        };
    }


    @Value("${http.client.retryCount:2}")
    private Integer retryCount = 2;

    @Bean
    public HttpRequestRetryHandler httpRequestRetryHandler() {
        //请求重试
        final int retryCount = this.retryCount;
        return (exception, executionCount, context) -> {
            //如果已经重试了 retryCount 次，就放弃
            if (executionCount >= retryCount) {
                return false;
            }
            //如果服务器丢掉了连接，那么就重试
            if (exception instanceof NoHttpResponseException) {
                return true;
            }
            //不要重试SSL握手异常
            if (exception instanceof SSLHandshakeException) {
                return false;
            }
            //超时
            if (exception instanceof InterruptedIOException) {
                return false;
            }
            //目标服务器不可达
            if (exception instanceof UnknownHostException) {
                return false;
            }
            //连接被拒绝
            if (exception instanceof ConnectTimeoutException) {
                return false;
            }
            //SSL握手异常
            if (exception instanceof SSLException) {
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            //如果请求是幂等的，就再次尝试
            if (!(request instanceof HttpEntityEnclosingRequest)) {
                return true;
            }
            return false;
        };
    }


    /**
     * 连接超时时间（单位：毫秒）
     */
    @Value("${http.client.connectTimeout:30000}")
    private Integer connectTimeout = 30000;

    /**
     * 从连接池中取连接的超时时间（单位：毫秒）
     */
    @Value("${http.client.connectionRequestTimeout:1000}")
    private Integer connectionRequestTimeout = 1000;

    /**
     * 请求超时时间（单位：毫秒）
     */
    @Value("${http.client.socketTimeout:90000}")
    private Integer socketTimeout = 90000;

    @Bean
    public RequestConfig requestConfig() {
        return RequestConfig.custom()
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout)
                .build();
    }
}
